package com.changpei.search.service.impl;

import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import com.changpei.common.cache.ContentCache;
import com.changpei.common.cache.UserCache;
import com.changpei.common.result.Result;
import com.changpei.common.utils.DateUtils;
import com.changpei.common.utils.FilePrefix;
import com.changpei.common.utils.JsonUtil;
import com.changpei.common.utils.PageUtil;
import com.changpei.core.feign.*;
import com.changpei.core.pojo.*;
import com.changpei.search.config.RecommendConfig;
import com.changpei.search.dao.ContentIndexMapper;
import com.changpei.search.dao.HomeIndexMapper;
import com.changpei.search.pojo.ContentIndex;
import com.changpei.search.pojo.HomeIndex;
import com.changpei.search.pojo.UserIndex;
import com.changpei.search.service.ContentIndexService;
import com.changpei.search.service.UserIndexService;
import com.changpei.search.vo.ContentIndexVO;
import com.changpei.search.vo.HomeIndexVO;
import lombok.extern.slf4j.Slf4j;
import org.assertj.core.util.Lists;
import org.elasticsearch.index.query.BoolQueryBuilder;
import org.elasticsearch.index.query.QueryBuilders;
import org.elasticsearch.index.query.TermsQueryBuilder;
import org.elasticsearch.search.SearchHit;
import org.elasticsearch.search.aggregations.AggregationBuilders;
import org.elasticsearch.search.aggregations.bucket.terms.Terms;
import org.elasticsearch.search.aggregations.bucket.terms.TermsAggregationBuilder;
import org.elasticsearch.search.aggregations.metrics.tophits.InternalTopHits;
import org.elasticsearch.search.aggregations.metrics.tophits.TopHitsAggregationBuilder;
import org.elasticsearch.search.sort.SortOrder;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.PageRequest;
import org.springframework.data.elasticsearch.core.ElasticsearchTemplate;
import org.springframework.data.elasticsearch.core.aggregation.AggregatedPage;
import org.springframework.data.elasticsearch.core.query.Criteria;
import org.springframework.data.elasticsearch.core.query.CriteriaQuery;
import org.springframework.data.elasticsearch.core.query.NativeSearchQueryBuilder;
import org.springframework.stereotype.Service;
import org.springframework.util.ObjectUtils;

import java.util.*;
import java.util.stream.Collectors;

/**
 *
 * <p>Title: Content</p>
 * <p>Description: Content业务层接口实现类</p>
 * @author weibiao
 */
@Service
@Slf4j
public class ContentIndexServiceImpl implements ContentIndexService {

    @Autowired
    private ContentFeign contentFeign;

    @Autowired
    private ContentIndexMapper contentIndexMapper;

    @Autowired
    private ContentFileFeign contentFileFeign;

    @Autowired
    private FileFeign fileFeign;

    @Autowired
    private TagsFeign tagsFeign;

    @Autowired
    private UserIndexService userIndexService;

    @Autowired
    private UserFeign userFeign;

    @Autowired
    private UserCache userCache;

    @Autowired
    private ContentCache contentCache;

    @Autowired
    private ElasticsearchTemplate esTemplate;

    @Autowired
    private ContentTagsFeign contentTagsFeign;

    @Autowired
    private ContentTmpFeign contentTmpFeign;

    @Autowired
    private RecommendConfig config;

    @Autowired
    private HomeIndexMapper homeIndexMapper;

    @Override
    public void importData() {
        try {
            // 正式库数据集
            List<Content> contents = new ArrayList<>();
            String isValid = "http";
            List<ContentTmp> tmpList = contentTmpFeign.findAll().getData();

            // es库文件
            List<ContentIndex> contentIndices = new ArrayList<>();
            for (ContentTmp tmp : tmpList) {

                List<JSONObject> contentFiles = getContentFiles(tmp.getId());

                // 如果数据为空，则视为无效数据
                if (ObjectUtils.isEmpty(contentFiles)) {
                    continue;
                }

                // 更新正式库
                Content content = new Content();
                BeanUtils.copyProperties(tmp, content);
                contents.add(content);

                // 导入es库
                ContentIndex contentIndex = JsonUtil.jsonToBean(tmp, ContentIndex.class);
                User userInfo = getUserInfo(content.getUserId());
                contentIndex.setFiles(contentFiles);
                if (!ObjectUtils.isEmpty(contentFiles)) {
                    // 设置第一张图片为封面
                    contentIndex.setCoveredImg(contentFiles.get(0));
                }
                contentIndex.setTags(getContentTags(content.getId()));
                contentIndex.setNickName(userInfo.getNickName());
                if (!userInfo.getAvatarUrl().contains(isValid)) {
                    contentIndex.setAvatarUrl(FilePrefix.AVATAR_URL_PREFIX + userInfo.getAvatarUrl());
                } else {
                    contentIndex.setAvatarUrl(userInfo.getAvatarUrl());
                }
                contentIndices.add(contentIndex);
            }

            // 导入到主页库中
//            for (ContentIndex contentIndex : contentIndices) {
//                boolean existHome = getHomeIndex(contentIndex.getUserId());
//                if (existHome) {
//                    // 添加到主页库中
//                    HomeIndex homeIndex = new HomeIndex();
//                    BeanUtils.copyProperties(contentIndex, homeIndex);
//                    homeIndexMapper.save(homeIndex);
//                }
//            }

            System.out.println("---------------" + contentIndices.size());

            System.out.println("---------------" + contents.size());

            contentIndexMapper.saveAll(contentIndices);
//
            contentFeign.addAll(contents);
//
//            // 清空临时表
            contentTmpFeign.truncate();
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    /**
     * 校验是否存在首页库中
     * @param userId
     * @return
     */
    private boolean getHomeIndex(Long userId) {
        Criteria criteria = new Criteria("userId");
        criteria.is(userId);
        CriteriaQuery criteriaQuery = new CriteriaQuery(criteria);
        HomeIndexVO homeIndexVO = esTemplate.queryForObject(criteriaQuery, HomeIndexVO.class);
        return ObjectUtils.isEmpty(homeIndexVO);
    }

    @Override
    public Result<String> save(ContentIndex contentIndex) {
        try {
            contentIndexMapper.save(contentIndex);
            return Result.ok();
        } catch (Exception e) {
            log.warn("Content数据新增ES库失败", e.getMessage());
            return Result.failed();
        }
    }

    @Override
    public Result<String> update(ContentIndex contentIndex) {
        try {
            contentIndexMapper.save(contentIndex);
            return Result.ok();
        } catch (Exception e) {
            log.warn("Content数据更新ES库失败", e.getMessage());
            return Result.failed();
        }
    }

    @Override
    public Result<String> deleteById(Long id) {
        try {
            contentIndexMapper.deleteById(id);
            return Result.ok();
        } catch (Exception e) {
            log.warn("删除Content - ES库失败", e.getMessage());
            return Result.failed();
        }
    }

    @Override
    public ContentIndex findById(Long id) {
        return contentIndexMapper.findById(id).get();
    }

    @Override
    public List<ContentIndex> findAll() {
        return Lists.newArrayList(contentIndexMapper.findAll());
    }

    /**
     * 获取内容图片信息
     *
     * @param contentId
     * @return
     */
    @Override
    public List<JSONObject> getContentFiles(Long contentId) {
        ContentFile searchParam = new ContentFile();
        searchParam.setContentId(contentId);
        // 查询关联表资源
        List<Long> fileIds = contentFileFeign.findList(searchParam)
                .getData()
                .stream()
                .map(ContentFile::getFileId)
                .collect(Collectors.toList());
        if (!ObjectUtils.isEmpty(fileIds)) {
            // 获取关联信息
            return fileFeign.findByIds(fileIds)
                    .getData()
                    .stream()
                    .map(file -> {
                        JSONObject jsonObject = new JSONObject();
                        jsonObject.put("url", FilePrefix.CONTENT_URL_PREFIX + file.getUrl());
                        jsonObject.put("width", file.getWidth());
                        jsonObject.put("height", file.getHeight());
                        return jsonObject;
                    })
                    .collect(Collectors.toList());
        }
        return null;
    }

    /**
     * 获取内容标签信息
     *
     * @param contentId
     * @return
     */
    @Override
    public List<String> getContentTags(Long contentId) {
        ContentTags contentTags = new ContentTags();
        contentTags.setContentId(contentId);

        List<ContentTags> contentTagsList = contentTagsFeign.findList(contentTags).getData();
        if (!contentTagsList.isEmpty()) {
            List<Long> tagIds = contentTagsList.stream()
                    .map(ContentTags::getTagId)
                    .collect(Collectors.toList());

            List<Tags> tags = tagsFeign.findByIds(tagIds).getData();
            return tags.stream()
                    .map(Tags::getValue)
                    .collect(Collectors.toList());
        }

        return null;
    }

    /**
     * 获取用户信息
     *
     * @param id
     * @return
     */
    @Override
    public User getUserInfo(Long id) {
        UserIndex userIndex = userIndexService.findById(id);
        if (!ObjectUtils.isEmpty(userIndex)) {
            return JsonUtil.jsonToBean(userIndex, User.class);
        } else {
            return userFeign.findById(id).getData();
        }
    }

    @Override
    public Map<String, Object> history(Map<String, Object> map) {

        Map<String, Object> result = new HashMap<>(2);

        String token = map.get("token").toString();

        LinkedList<JSONObject> history = userCache.getViewHistory(userCache.getUserId(token));

        Page<ContentIndex> indexPage = historyCollectionQuery(map, history);
        if (ObjectUtils.isEmpty(indexPage)) {
            result.put("total", 0);
            result.put("content", new LinkedList<>());
            return result;
        }

        String hitKey = "contentId";

        List<JSONObject> list = indexPage.getContent().stream().map(c -> {
                    JSONObject o = JSONObject.parseObject(JSON.toJSONString(c));
                    history.forEach(h -> {
                        if (c.getId().equals(h.getLong(hitKey))) {
                            o.put("viewTime", h.getLong("viewTime"));
                        }
                    });
                    return o;
                }).sorted((o1, o2) -> Long.compare(o2.getLong("viewTime"), o1.getLong("viewTime")))
                .collect(Collectors.toList());

        result.put("total", indexPage.getTotalElements());
        result.put("content", list);

        return result;
    }

    @Override
    public Map<String, Object> collect(Map<String, Object> map) {

        Map<String, Object> result = new HashMap<>(2);

        String token = map.get("token").toString();

        List<Object> collection = contentCache.myCollection(userCache.getUserId(token));
        List<JSONObject> myCollection = collection
                .stream()
                .map(o -> JSONObject.parseObject(o.toString()))
                .collect(Collectors.toList());

        Page<ContentIndex> indexPage = historyCollectionQuery(map, myCollection);

        if (ObjectUtils.isEmpty(indexPage)) {
            result.put("total", 0);
            result.put("content", new LinkedList<>());
            return result;
        }

        result.put("total", indexPage.getTotalElements());
        result.put("content", indexPage.getContent());
        return result;
    }

    @Override
    public void hot() {

        Set<String> hotWords = new HashSet<>();

        NativeSearchQueryBuilder nativeSearchQueryBuilder = prepareRecommendSearch(true);
        AggregatedPage<ContentIndex> contentIndices = esTemplate.queryForPage(nativeSearchQueryBuilder.build(), ContentIndex.class);
        Terms hot = contentIndices.getAggregations().get("common");

        List<ContentIndex> list = processPrepareSearchData(hot);

        for (ContentIndex vo : list) {
            if (!ObjectUtils.isEmpty(vo.getTags())) {
                hotWords.addAll(vo.getTags());
            }
        }

        List<String> strings = PageUtil.startPage(new ArrayList<>(hotWords), 1, 20);
        // 设置热搜词
        contentCache.setHotWords(strings);

        List<JSONObject> objects = list.stream().map(o -> JSONObject.parseObject(JSON.toJSONString(o))).collect(Collectors.toList());
        // 设置热门数据
        contentCache.setHot(objects);

    }

    @Override
    public Page<ContentIndexVO> search(String content, int pageNum, int pageSize) {
        // 查询构建器
        NativeSearchQueryBuilder nb = new NativeSearchQueryBuilder();

        // 构建布尔查询
        BoolQueryBuilder boolQuery = QueryBuilders.boolQuery();
        boolQuery.should(QueryBuilders.matchQuery("content", content))
                .should(QueryBuilders.matchQuery("tags", content));
        nb.withQuery(boolQuery);
        nb.withPageable(PageRequest.of(pageNum - 1, pageSize));
        // 执行查询
        return esTemplate.queryForPage(nb.build(), ContentIndexVO.class);
    }

    public Page<ContentIndex> historyCollectionQuery(Map<String, Object> map, List<JSONObject> source) {
        int pageNum = Integer.parseInt(map.get("pageNum").toString());
        int pageSize = Integer.parseInt(map.get("pageSize").toString());

        if (source.isEmpty()) {
            return null;
        }

        Set<Long> ids = source.stream().map(e -> e.getLong("contentId")).collect(Collectors.toSet());
        TermsQueryBuilder idsQuery = QueryBuilders.termsQuery("id", ids);
        NativeSearchQueryBuilder nativeSearchQueryBuilder = new NativeSearchQueryBuilder();
        nativeSearchQueryBuilder.withQuery(idsQuery);
        nativeSearchQueryBuilder.withPageable(PageRequest.of(pageNum - 1, pageSize));
        return contentIndexMapper.search(nativeSearchQueryBuilder.build());
    }

    /**
     * 我的锁定数据
     * @param userId
     * @return
     */
    @Override
    public Set<Long> myLockData(Long userId) {
        // 我的锁定
        Set<Long> lockIds = new HashSet<>();

        // 我的收藏
        Set<Long> collectIds = contentCache.myCollection(userId)
                .stream()
                .map(o -> JSONObject.parseObject(JSON.toJSONString(o)).getLong("contentId"))
                .collect(Collectors.toSet());

        // 我的浏览历史
        Set<Long> viewContentIds = userCache.getViewHistory(userId)
                .stream()
                .map(view -> view.getLong("contentId"))
                .collect(Collectors.toSet());

        // 临时锁定
        String end = DateUtils.getFormatDateTime(new Date());
        Set<Object> tmpLockData = userCache.getTmpLockData(userId);
        tmpLockData.forEach(lockData -> {
            JSONObject object = JSON.parseObject(JSON.toJSONString(lockData));
            String start = object.getString("lockTime");
            long minutes = DateUtils.calculationMinutes(start, end);
            if (minutes < config.getLockTime()) {
                lockIds.add(object.getLong("contentId"));
            } else {
                userCache.removeTmpLockData(userId, lockData);
            }
        });


        lockIds.addAll(collectIds);
        lockIds.addAll(viewContentIds);

        return lockIds;
    }

    @Override
    public Set<ContentIndexVO> detailRecommend(Map<String, Object> map) {

        Set<ContentIndexVO> list = new LinkedHashSet<>();

        String token = map.get("token").toString();
        Long userId = userCache.getUserId(token);
        Long contentUserId = Long.parseLong(map.get("userId").toString());
        Long contentId = Long.parseLong(map.get("contentId").toString());
        String content = map.get("content").toString();

        Set<Long> set = myLockData(userId);

        // 内容用户ID查询1个
        NativeSearchQueryBuilder contentUserNb = new NativeSearchQueryBuilder();
        BoolQueryBuilder contentUserBoolQuery = new BoolQueryBuilder();
        // 移除数据周期
        contentUserBoolQuery.mustNot(QueryBuilders.termsQuery("id", set))
                .mustNot(QueryBuilders.termQuery("id", contentId))
                .must(QueryBuilders.termQuery("userId", contentUserId));
        contentUserNb.withQuery(contentUserBoolQuery);
        contentUserNb.withPageable(PageRequest.of(0, 1));
        Page<ContentIndexVO> contentUserVos = esTemplate.queryForPage(contentUserNb.build(), ContentIndexVO.class);

        // 内容标题推荐14个
        NativeSearchQueryBuilder contentNb = new NativeSearchQueryBuilder();
        BoolQueryBuilder contentBoolQuery = new BoolQueryBuilder();
        contentBoolQuery.mustNot(QueryBuilders.termsQuery("id", set))
                .mustNot(QueryBuilders.termQuery("id", contentId))
                .should(QueryBuilders.matchQuery("content", content));
        contentNb.withQuery(contentBoolQuery);
        contentNb.withPageable(PageRequest.of(0, 14));
        Page<ContentIndexVO> contentIndexVos = esTemplate.queryForPage(contentNb.build(), ContentIndexVO.class);

        // 用户标签推荐5个
        NativeSearchQueryBuilder userTagsNb = new NativeSearchQueryBuilder();
        BoolQueryBuilder userTagsBoolQuery = new BoolQueryBuilder();

        List<String> tags = JSONObject.parseArray(map.get("tags").toString(), String.class);
        StringBuilder sb = new StringBuilder();
        tags.forEach(sb::append);
        userTagsBoolQuery
                .mustNot(QueryBuilders.termsQuery("id", set))
                .mustNot(QueryBuilders.termQuery("id", contentId))
                .should(QueryBuilders.matchQuery("tags", sb.toString()));

        userTagsNb.withQuery(userTagsBoolQuery);
        userTagsNb.withPageable(PageRequest.of(0, 5));
        // 执行查询
        Page<ContentIndexVO> userTagsContentVos = esTemplate.queryForPage(userTagsNb.build(), ContentIndexVO.class);
        List<ContentIndexVO> tagsContentVos = userTagsContentVos.getContent();

        list.addAll(contentUserVos.getContent());
        list.addAll(contentIndexVos.getContent());
        list.addAll(tagsContentVos);

        return list;
    }

    @Override
    public Page<ContentIndexVO> getUserContents(Long userId, Integer pageNum, Integer pageSize) {
        NativeSearchQueryBuilder nb = new NativeSearchQueryBuilder();
        BoolQueryBuilder boolQueryBuilder = new BoolQueryBuilder();
        boolQueryBuilder.must(QueryBuilders.termQuery("userId", userId));
        nb.withQuery(boolQueryBuilder);
        nb.withPageable(PageRequest.of(pageNum - 1, pageSize));
        return esTemplate.queryForPage(nb.build(), ContentIndexVO.class);
    }

    @Override
    public void clearTmpLockData() {
        List<UserIndex> userIndexList = userIndexService.findAll();
        for (UserIndex userIndex : userIndexList) {
            String end = DateUtils.getFormatDateTime(new Date());
            Set<Object> tmpLockData = userCache.getTmpLockData(userIndex.getId());
            if (!ObjectUtils.isEmpty(tmpLockData)) {
                for (Object lockData : tmpLockData) {
                    JSONObject object = JSON.parseObject(JSON.toJSONString(lockData));
                    String start = object.getString("lockTime");
                    long minutes = DateUtils.calculationMinutes(start, end);
                    if (minutes > config.getLockTime()) {
                        userCache.removeTmpLockData(userIndex.getId(), lockData);
                    }
                }
            }
        }
    }

    public NativeSearchQueryBuilder prepareRecommendSearch(boolean sort) {

        NativeSearchQueryBuilder nativeSearchQueryBuilder = new NativeSearchQueryBuilder();
        TermsAggregationBuilder aggregationBuilder = AggregationBuilders.terms("common").field("userId").size(1000);
        TopHitsAggregationBuilder hotScoreHits = AggregationBuilders.topHits("common_score_hits").size(1000);
        hotScoreHits.size(1);

        if (sort) {
            hotScoreHits.sort("likedNum", SortOrder.DESC);
            hotScoreHits.sort("collectedNum", SortOrder.DESC);
            hotScoreHits.sort("commentedNum", SortOrder.DESC);
        }

        aggregationBuilder.subAggregation(hotScoreHits);
        nativeSearchQueryBuilder.addAggregation(aggregationBuilder);
        return nativeSearchQueryBuilder;
    }

    private List<ContentIndex> processPrepareSearchData(Terms common) {
        List<ContentIndex> list = new ArrayList<>();
        if (ObjectUtils.isEmpty(common)) {
            return new ArrayList<>();
        }

        for (Terms.Bucket bucket : common.getBuckets()) {
            InternalTopHits commonScoreHits = bucket.getAggregations().get("common_score_hits");
            SearchHit hit = commonScoreHits.getHits().getAt(0);
            JSONObject jsonObject = JSONObject.parseObject(hit.toString());
            ContentIndex source = jsonObject.getObject("_source", ContentIndex.class);
            list.add(source);
        }
        return list;
    }
}
